Value Object
1つのprimitive値や、値の合成物を、値(value)として扱うobject
objectというのは、OOPの文脈ではclass
FPの文脈ではnewtypeした値など
合成物であっても、primitive値と同等の扱いができればValueObject
個別にIdentityを持たずに、値のみを見て比較する
FPの文脈に慣れている人は、当たり前過ぎて逆に何言ってるかわからなくなりそうmrsekut.icon
参照が絡む言語を説明にするために文章が長くなっている
FP言語向けには、以下の説明文で十分
永続的なidentityを
持つobjectはEntity
持たないobjectはValue Object
合成物かどうかももはや気にする必要はないmrsekut.icon
なぜ必要になるか?
primitive値でも良いが、特に合成物を考えた方がわかりやすい
例えば、「xy座標」という概念を扱うことを考える
これは、x座標とy座標という2つの値の合成物である
例えば、(1,2)と(1,2)という2つのデータがあった時に、これらを等価と見なしたい
この、合成物同士の値だけを見て比較できれば、Value Objectの目的は達成している
FP的に表現すれば自然と等価になる
このPointはValue Objectと言える
code:hs
data Point = Point (Int, Int) deriving (Eq)
Point (1,2) == Point (1,2) -- True
何も特殊なことはしていないmrsekut.icon
合成物である必要もない
code:fs
let widgetCode1 = WidgetCode "W1234"
let widgetCode2 = WidgetCode "W1234"
printfn "%b" (widgetCode1 = WidgetCode2) // "true"
特別にequalの実装を加える必要はない
代数的データ型の構造が同じならば同じものだと判断される
必要であれば、equalの定義を変えることもできる
参照が関わる言語では、少し考えることが増える
以下の話は、言語に「構造を見て等価性をチェックできる機能があるかどうか」というだけmrsekut.icon
構造だけを見た自明な比較をする演算子等が用意されていない
演算子==や===は、構造ではなく、参照の等価性を見ている
同じ構造の値を比較していても、参照が異なるとtrueにならない
code:ts
const p1 = {x: 2, y: 3};
const p2 = {x: 2, y: 3};
p1 === p2 // false
従って、構造の等価性を判断する演算を自分で定義する必要がある
code:ts
const equalPoint = (p1: Point, p2: Point) => p1.x === p2.x && p1.y === p2.y
tsだとoverloadするわけにも行かないので、名前を変えて個別にそういう関数が必要
classなら、一律でequal()とかを用意しておけば良い
code:ts
class Point {
constructor(private x: number, private y: number) {}
equals(other: Point) {
return this.x === other.x && this.y === other.y;
}
}
これで、複合物も値だけを見た比較ができるようになる
code:ts
const p1 = new Point(1,2);
const p2 = new Point(1,2);
p1.equals(p2) // true
以上で一応ValueObjectの定義は満たしている
しかし、これだけでは参照の取り扱いに難があるので、更に工夫を推奨される
これを回避するためにimmutableにすると良い
値を変更するのではなく、新しく作成する
言語がそういう機能を用意するケースもある
間違いやすいポイント
ただprimitive値や合成物を包めばValueObjectになるわけではない
ただ値を包むだけでは、まだIdentityで比較する余地を残してしまう
包んだ上で、構造だけを見たobject同士の比較ができる必要がある
Value Objectは結果的にValueを包んだObjectにはなるが、考える順序が逆ということmrsekut.icon
何でもかんでもprimitive値を全てclassでwrapしろという話ではない
「project内でIntを素のまま使うな、全部wrapしろ」というやつ
そもそもこれは、ValueObjectの定義というか、使い方の話なのであまり重要ではないmrsekut.icon
必要であればすればいい
全部wrapするのが適切である状況もあると思う
具体例
付随的に得られる嬉しさ
参考
Martin Fowler
普通にめちゃくちゃわかりやすいので最初に読めば良いと思うmrsekut.icon
F#なので、説明がシンプルで良い
Entityとの比較がメイン
具体的な実装方法については書かれていない
後半は、誤解などについて
DDDではないっぽいが似たようなことが書かれているらしい
長い